/*
 * Copyright (C) 2010 Young, Fey <fey.young@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package com.youngfey.ebs

/**
 * @author Young, Fey <fey.young@gmail.com>
 */
class Analyst {
    static Number majorityLowBound = 0.2
    static Number majorityHighBound = 0.8
    /**
     * @return probability of archive given steps in given estimate time (hours)
     */
    Number getProbability(int step, Number estimate) {
        def d = getDistribution(step)
        def pfu = d[-1]
        def index = d.size() - 1
        def pfl
        if(pfu.estimate <= estimate) return 1
        while(index >= 0) {
            pfl = d[index--]
            if(pfl.estimate == estimate) {
                return pfl.probability
            }

            if(pfl.estimate > estimate) {
                pfu = pfl
                continue
            }

            if(pfl.estimate < estimate) {
                return getGeoValue(
                    pfu.estimate, pfl.estimate,
                    pfu.probability, pfl.probability,
                    estimate)
            }
        }
        
        0
    }

    /**
     * @return estimate time of archive given steps for given probability
     */
    Number getEstimate(int step, Number minProbability) {
        assert minProbability > 0 && minProbability <= 1
        def d = getDistribution(step)
        def pfu = d[-1]
        if(minProbability == 1) return pfu.estimate
        def index = d.size() - 1
        def pfl = pfu
        while(index >= 0) {
            pfl = d[index--]
            if(pfl.probability == minProbability) {
                return pfl.estimate
            }

            if(pfl.probability > minProbability) {
                pfu = pfl
                continue
            }

            if(pfl.probability < minProbability) {
                return getGeoValue(
                    pfu.probability, pfl.probability,
                    pfu.estimate, pfl.estimate,
                    minProbability)
            }
        }
        
        pfl.estimate
    }

    /**
     * @return max time which given steps cost
     */
    Number getMaxEstimate(int step) {
        getDistribution(step)[-1].estimate
    }

    /**
     * @return min time which given steps cost
     */
    Number getMinEstimate(int step) {
        getDistribution(step)[0].estimate
    }

    /**
     * Estimates made by Estimator.estimate(steps)
     */
    List<List<Number>> estimates
    
    void setEstimates(List<List<Number>> estimates) {
        assert estimates.size() > 0
        this.estimates = estimates
        maxStep = estimates[0].size()
        
        distributions = []

        maxStep.times { step->
            def distribution = []
            def stepEstimates = estimates.collect({ it[0..step].sum() }).sort()
            def size = stepEstimates.size()
            stepEstimates << -1
            def e = stepEstimates[0]
            (size + 1).times { idx->
                if(stepEstimates[idx] != e) {
                    distribution << new PossibleFuture(e, idx / size) 
                    e = stepEstimates[idx]
                }
            }

            assert distribution.size() > 0
            distributions << distribution
        }
    }

    String getBrief() {
        if(!distributions) {
            return 'not yet inited'
        }

        def txts = []
        maxStep.times {
            txts << """To archive step ${it + 1} (from $majorityLowBound to $majorityHighBound):
        MIN : ${getMinEstimate(it)} | LOW : ${getEstimate(it, majorityLowBound)} ($majorityLowBound) | HIGH: ${getEstimate(it, majorityHighBound)} ($majorityHighBound) | MAX : ${getMaxEstimate(it)}"""
        }

        txts.join('\n')
    }

    String getAllStepsBrief() {
        if(!distributions) {
            return 'not yet inited'
        }
        """To archive step ${maxStep} (from $majorityLowBound to $majorityHighBound):
        MIN : ${getMinEstimate(maxStep - 1)} | LOW : ${getEstimate(maxStep - 1, majorityLowBound)} ($majorityLowBound) | HIGH: ${getEstimate(maxStep - 1, majorityHighBound)} ($majorityHighBound) | MAX : ${getMaxEstimate(maxStep - 1)}"""

    }

    private getGeoValue(hx, lx, hy, ly, x) {
        (hy * (x - lx) + ly * (hx - x)) / (hx - lx)
    }

    private getDistribution(step) {
        assert step >= 0 && step < maxStep : "Step = $step & MaxStep = $maxStep"
        distributions[step] 
    }

    int getMaxStep() {
        maxStep
    }

    private maxStep

    //[[PossibleFuture]]
    private distributions

    // For debug only
    @Override
    String toString() {
        "${this.class.name}$properties"
    }
}
